﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;

namespace Postnuclear.Dependency.Tree
  {
  /// <summary>
  /// Builds the dependency tree from input data.
  /// </summary>
  internal sealed class DependencyTreeBuilder
    {
    #region Public

    // Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="DependencyTreeBuilder"/> class.
    /// </summary>
    public DependencyTreeBuilder()
      {
      TreeRoots = new Dictionary<object, ICollection<Stack<MemberInfo>>>();
      ResetBranch();
      }

    // Methods

    /// <summary>
    /// Builds a dependency tree from registered values.
    /// </summary>
    /// <returns>Built up dependency branch root.</returns>
    public DependencyTree Build()
      {
      Contract.Ensures(Contract.Result<DependencyTree>() != null);

      Func<object, MemberInfo, IEnumerable<Stack<MemberInfo>>, IDependencyTreeElement<object>> handler = BuildConstantBranch;

      return new DependencyTree(
        (from root in TreeRoots.Keys
         from branch in Merge(TreeRoots[root])
         select handler.Method.ToGeneric(root.GetType()).Invoke<INotifyValueChanged>(null, root, branch.Key, branch)).ToArray());
      }

    /// <summary>
    /// Registers the constant element.
    /// </summary>
    /// <param name="value">The constant value.</param>
    public void RegisterConstant(object value)
      {
      Contract.Requires<ArgumentNullException>(Branch.Count == 0 || value != null);
      Contract.Requires<InvalidOperationException>(Branch.Count == 0 || !value.GetType().IsValueType);

      if (Branch.Count == 0)
        {
        return;
        }

      ICollection<Stack<MemberInfo>> branches;

      if (!TreeRoots.TryGetValue(value, out branches))
        {
        branches = new List<Stack<MemberInfo>>();
        TreeRoots.Add(value, branches);
        }

      branches.Add(Branch);

      ResetBranch();
      }

    /// <summary>
    /// Registers the member.
    /// </summary>
    /// <param name="member">The member.</param>
    public void RegisterMember(MemberInfo member)
      {
      Contract.Requires<ArgumentNullException>(member != null);
      Contract.Requires<ArgumentOutOfRangeException>(member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Field);
      Contract.Requires<InvalidOperationException>(Branch.Count == 0 || !member.GetMemberType().IsValueType);

      Branch.Push(member);
      }

    /// <summary>
    /// Resets the branch.
    /// </summary>
    public void ResetBranch()
      {
      Branch = new Stack<MemberInfo>();
      }

    #endregion

    #region Private

    // Types

    /// <summary>
    /// Performs comparison of members information equality.
    /// </summary>
    private class MemberEqualityComparer
      : IEqualityComparer<MemberInfo>
      {
      #region Public

      // Methods

      /// <summary>
      /// Determines whether the specified objects are equal.
      /// </summary>
      /// <param name="x">The first object of type <see cref="MemberInfo"/> to compare.</param>
      /// <param name="y">The second object of type <see cref="MemberInfo"/>to compare.</param>
      /// <returns><c>true</c> if the specified objects are equal; otherwise, <c>false</c>.</returns>
      public bool Equals(MemberInfo x, MemberInfo y)
        {
        return x.Name == y.Name && x.DeclaringType == y.DeclaringType;
        }

      /// <summary>
      /// Returns a hash code for this instance.
      /// </summary>
      /// <param name="obj">The object.</param>
      /// <returns>
      /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
      /// </returns>
      public int GetHashCode(MemberInfo obj)
        {
        return obj.GetHashCode();
        }

      #endregion
      }

    // Properties

    private Stack<MemberInfo> Branch
      {
      get;
      set;
      }

    // Methods

    private static IEnumerable<IGrouping<MemberInfo, Stack<MemberInfo>>> Merge(IEnumerable<Stack<MemberInfo>> source)
      {
      return source.Where(branch => branch.Count > 0).GroupBy(branch => branch.Peek(), Pop, new MemberEqualityComparer());
      }

    private static Stack<T> Pop<T>(Stack<T> source)
      {
      source.Pop();
      return source;
      }

    private static IDependencyTreeElement<TValue> BuildConstantBranch<TValue>(TValue holder, MemberInfo member, IEnumerable<Stack<MemberInfo>> branches)
      where TValue : class
      {
      IDependencyTreeElement<TValue> branch = BuildElementBranch<TValue>(member, branches);
      branch.UpdateHolderValue(holder);
      return branch;
      }

    private static IDependencyTreeElement<THolder> BuildElementBranch<THolder>(MemberInfo member, IEnumerable<Stack<MemberInfo>> branches)
      where THolder : class
      {
      if (branches.Any(branch => branch.Count != 0))
        {
        Func<MemberInfo, IEnumerable<Stack<MemberInfo>>, IDependencyTreeElement<THolder>> handler = BuildElementTree<THolder, object>;
        return handler.Method.ToGeneric(typeof(THolder), member.GetMemberType()).Invoke<IDependencyTreeElement<THolder>>(null, member, branches);
        }
      else
        {
        Func<MemberInfo, IDependencyTreeElement<THolder>> handler = BuildMemberElement<THolder, object>;
        return handler.Method.ToGeneric(typeof(THolder), member.GetMemberType()).Invoke<IDependencyTreeElement<THolder>>(null, member);
        }
      }

    private static IDependencyTreeElement<THolder> BuildElementTree<THolder, TValue>(MemberInfo member, IEnumerable<Stack<MemberInfo>> branches)
      where THolder : class
      where TValue : class
      {
      IDependencyTreeElement<TValue>[] children =
        (from branch in Merge(branches)
         select BuildElementBranch<TValue>(branch.Key, branch)).ToArray();

      return new DependencyTreeElement<THolder, TValue>(BuildMemberElement<THolder, TValue>(member), children);
      }

    private static IDependencyElement<THolder, TValue> BuildMemberElement<THolder, TValue>(MemberInfo member)
      where THolder : class
      {
      return new MemberDependencyElement<THolder, TValue>(member);
      }

    // Fields

    private readonly IDictionary<object, ICollection<Stack<MemberInfo>>> TreeRoots;

    #endregion
    }
  }